#define PrynEditorInternal
#define PrynEditorImplementation
#include <prynEditor.h>

#define IntersectionX(INTERSECTION) path->verticals [(INTERSECTION)->vertical].offset
#define IntersectionY(INTERSECTION) path->horizontals [(INTERSECTION)->horizontal].offset

#define channelSeparation 5

static int channelOffset (int index)
{
	return ((index + 1) / 2) * 5 * (index & 1 ? -1 : 1);
}

PrynEditorExport (int, prynEditorPathIntersectionX, (PrynEditorPath *path, PrynEditorPathIntersection *intersection))
{
	return path && intersection ? IntersectionX (intersection) : 0;
}

PrynEditorExport (int, prynEditorPathIntersectionY, (PrynEditorPath *path, PrynEditorPathIntersection *intersection))
{
	return path && intersection ? IntersectionY (intersection) : 0;
}

PrynEditorExport (void, prynEditorPathDestroy, (PrynState *state, PrynEditorPath *path))
{
	if (!path || !state) return;
	prynFree (state, path->intersections);
	prynFree (state, path->horizontals);
	prynFree (state, path->verticals);
	prynSetMemory (path, 0, sizeof (*path));
}

static bool EachAddConnectedPins (PrynEditorComponent *component, PrynPin *pin, int x, int y, void *data)
{
	if (PrynIsTrue (prynPinIsConnected (pin)))
		(*(int *) data) ++;
	return true;
}

static bool EachInsertConnectedPins (PrynEditorComponent *component, PrynPin *pin, int x, int y, void *data)
{
	PrynEditorPathLine **line = (PrynEditorPathLine **) data;
	
	if (PrynIsTrue (prynPinIsConnected (pin)))
	{
		(*line)->offset = x + prynEditorPinSizeHalf;
		(*line)->collapsable = false;
		(*line) ++;
	}
	return true;
}

static void FilterLines (PrynEditorPathLine *lines, size_t *count)
{
	PrynEditorPathLine *input, *output, *end;

	for (input = lines, output = lines, end = lines + *count; input < end; input ++)
	{
		*output ++ = *input;
		if (input + 1 >= end || input [1].offset - input [0].offset > channelSeparation)
			continue;

		// If both are collapsable, average them. If one is collapsable, collapse into it. If neither are collapsable, don't collapse.
		if (input [0].collapsable)
		{
			if (input [1].collapsable)
				output [0].offset = (input [0].offset + input [1].offset) >> 1;
			else
				output [0].offset = input [1].offset;
		}
		else if (input [1].collapsable)
			output [0].offset = input [0].offset;
		else
			continue;
	}

	*count = (size_t) (output - lines);
}

PrynEditorExport (PrynResult, prynEditorPathSetup, (PrynEditorBoard *board, PrynEditorPath *path))
{
	PrynState *state;
	size_t componentCount = 0, pinCount = 0;
	PrynEditorComponent *component;
	PrynEditorPathLine *outputVertical, *outputHorizontal;
	size_t index = 0, horizontal, vertical;
	PrynRectangle area;

	if (!board || !path) return PrynResultNullArgument;
	state = board->state;
	
	prynDListEach (component, board->components)
	{
		componentCount ++;
		prynEditorComponentEachPin (component, &EachAddConnectedPins, &pinCount);
	}
	
	path->open.first = path->open.last = 0, path->closed.first = path->closed.last = 0;
	path->horizontalCount = componentCount * 2 * 3;
	path->verticalCount = componentCount * 2 * 3 + pinCount;
	path->intersectionCount = path->horizontalCount * path->verticalCount;
	
	if (path->horizontalCount > path->horizontalAllocated)
	{
		path->horizontalAllocated = path->horizontalCount;
		prynFree (state, path->horizontals);
		prynAllocate (state, sizeof (*path->horizontals) * path->horizontalCount, (void **) &path->horizontals);
		if (!path->horizontals)
		{
			path->horizontalCount = path->horizontalAllocated = 0;
			return PrynResultOutOfMemory;
		}
	}
	
	if (path->verticalCount > path->verticalAllocated)
	{
		path->verticalAllocated = path->verticalCount;
		prynFree (state, path->verticals);
		prynAllocate (state, sizeof (*path->verticals) * path->verticalCount, (void **) &path->verticals);
		if (!path->verticals)
		{
			path->verticalCount = path->verticalAllocated = 0;
			return PrynResultOutOfMemory;
		}
	}
	
	if (path->intersectionCount > path->intersectionAllocated)
	{
		path->intersectionAllocated = path->intersectionCount;
		prynFree (state, path->intersections);
		prynAllocateCleared (state, sizeof (*path->intersections) * path->intersectionCount, (void **) &path->intersections);
		if (!path->intersections)
		{
			path->intersectionCount = path->intersectionAllocated = 0;
			return PrynResultOutOfMemory;
		}
	}
	prynClearMemory (path->intersections, sizeof (*path->intersections) * path->intersectionCount);
	
	outputVertical = path->verticals, outputHorizontal = path->horizontals;
	
	// Add the horizontal and vertical lines around the components.
	prynDListEach (component, board->components)
	{
		int m;
		#define addLine(DIRECTION, OFFSET) \
			output##DIRECTION->offset = (OFFSET), output##DIRECTION->collapsable = true, output##DIRECTION ++
		#define addElement(DIRECTION, VALUE) \
			addLine (DIRECTION, m = (VALUE)); \
			addLine (DIRECTION, m - channelSeparation); \
			addLine (DIRECTION, m + channelSeparation)
		
		addElement (Horizontal, component->y - PrynEditorComponentMargin);
		addElement (Horizontal, component->y + component->h + PrynEditorComponentMargin);
		addElement (Vertical, component->x - PrynEditorComponentMargin);
		addElement (Vertical, component->x + component->w + PrynEditorComponentMargin);
		#undef addElement
		#undef addLine
	}
	
	// Add the vertical lines for the pins.
	prynDListEach (component, board->components)
		prynEditorComponentEachPin (component, &EachInsertConnectedPins, &outputVertical);
		
	// Adjust counts.
	path->horizontalCount = (size_t) (outputHorizontal - path->horizontals);
	path->verticalCount = (size_t) (outputVertical - path->verticals);

	// Sort horizontal and vertical lines.
	prynSort (path->horizontals, sizeof (*path->horizontals), path->horizontalCount, prynOrderInteger (), 0);
	prynSort (path->verticals, sizeof (*path->verticals), path->verticalCount, prynOrderInteger (), 0);
	
	// Remove any overlapping lines.
	FilterLines (path->horizontals, &path->horizontalCount);
	FilterLines (path->verticals, &path->verticalCount);
	
	if (0)
	{
		size_t i;

		prynPrintFormat (state, "horizontal:");
		for (i = 0; i < path->horizontalCount; i ++) prynPrintFormat (state, " %d", path->horizontals [i].offset);
		prynPrintFormat (state, "\nvertical:");
		for (i = 0; i < path->verticalCount; i ++) prynPrintFormat (state, " %d", path->verticals [i].offset);
		prynPrintFormat (state, "\n");
	}
	
	#define IndexOf(HORIZONTAL, VERTICAL) (path->intersections + (HORIZONTAL) * path->verticalCount + (VERTICAL))
	#define Overlapping(SX, SY, EX, EY) ((prynRectangle (&area, (SX) - PrynEditorComponentMargin + 1, (SY) - PrynEditorComponentMargin + 1, (EX) + PrynEditorComponentMargin - 1, (EY) + PrynEditorComponentMargin - 1), prynEditorBoardFirstOverlappingComponent (board, 0, &area)) == 0)
	
	// Link the intersections together.
	for (index = 0, horizontal = 0; horizontal < path->horizontalCount; horizontal ++)
		for (vertical = 0; vertical < path->verticalCount; vertical ++)
		{
			PrynEditorPathIntersection *intersection = &path->intersections [index];
			int x = path->verticals [vertical].offset, y = path->horizontals [horizontal].offset;
			
			intersection->horizontal = horizontal;
			intersection->vertical = vertical;
			if (horizontal && !Overlapping (x, path->horizontals [horizontal - 1].offset, x, y))
				intersection->up = IndexOf (horizontal - 1, vertical);
			if (horizontal < path->horizontalCount - 1 && !Overlapping (x, y, x, path->horizontals [horizontal + 1].offset))
				intersection->down = IndexOf (horizontal + 1, vertical);
			if (vertical && !Overlapping (path->verticals [vertical - 1].offset, y, x, y))
				intersection->left = IndexOf (horizontal, vertical - 1);
			if (vertical < path->verticalCount - 1 && !Overlapping (x, y, path->verticals [vertical + 1].offset, y))
				intersection->right = IndexOf (horizontal, vertical + 1);
			index ++;
		}
	#undef Overlapping
	#undef IndexOf
	
	return PrynResultSuccess;
}

PrynEditorExport (PrynEditorPathIntersection *, prynEditorPathIntersectionAt, (PrynEditorPath *path, size_t horizontal, size_t vertical))
{
	if (!path || horizontal >= path->horizontalCount || vertical >= path->verticalCount)
		return 0;
	return path->intersections + path->verticalCount * horizontal + vertical;
}

PrynEditorExport (bool, prynEditorPathIntersectionIsConnected, (PrynEditorPathIntersection *intersection))
{
	return intersection && (intersection->left || intersection->right || intersection->up || intersection->down);
}

PrynEditorExport (PrynEditorPathIntersection *, prynEditorPathFindClosestIntersection, (PrynEditorPath *path, int x, int y, bool down))
{
	size_t ix, iy;
	
	if (!path) return 0;
	for (ix = 0; ix < path->verticalCount - 1; ix ++)
		if (path->verticals [ix + 1].offset > x)
			break;
	for (iy = 0; iy < path->horizontalCount - 1; iy ++)
		if (path->horizontals [iy + 1].offset >= y)
			break;
	if (down)
	{
		iy ++;
		while (iy < path->horizontalCount - 1 && !prynEditorPathIntersectionIsConnected (prynEditorPathIntersectionAt (path, iy, ix)))
			iy ++;
	}
	else
		while (iy > 0 && !prynEditorPathIntersectionIsConnected (prynEditorPathIntersectionAt (path, iy, ix)))
			iy --;
	
	return prynEditorPathIntersectionAt (path, iy, ix);
}

PrynEditorExport (PrynResult, prynEditorPathComputeRoute, (PrynState *state, PrynEditorPath *path, int sx, int sy, int ex, int ey, PrynEditorPathRoute *route))
{
	#define Estimate(X, Y) (((X) - ex) * ((X) - ex) + ((Y) - ey) * ((Y) - ey))
	#define Score(INTERSECTION) ((INTERSECTION)->score + (INTERSECTION)->estimate)
	
	#define PushOpen(INTERSECTION) \
		do { \
			PrynEditorPathIntersection *POINTER = (INTERSECTION); \
			POINTER->opened = true, POINTER->closed = false; \
			prynDListAppend (path->open, POINTER); \
		} while (0)
			
	#define PushClosed(INTERSECTION) \
		do { \
			PrynEditorPathIntersection *POINTER = (INTERSECTION); \
			POINTER->opened = false, POINTER->closed = true; \
			prynDListAppend (path->closed, POINTER); \
		} while (0)

	PrynEditorPathIntersection *start, *end, *intersection;
	
	if (!state || !path || !route) return PrynResultNullArgument;
	route->steps = 0;
	route->count = 0;
	route->sx = sx;
	route->sy = sy;
	route->ex = ex;
	route->ey = ey;
	route->path = path;
	
	start = prynEditorPathFindClosestIntersection (path, sx, sy, true);
	end = prynEditorPathFindClosestIntersection (path, ex, ey, false);
	
	// Reset the intersection node state for pathfinding and initialise it.
	prynForEach (intersection, path->intersections, path->intersectionCount)
	{
		intersection->next = intersection->last = 0, intersection->score = PrynEditorPathScoreInvalid;
		intersection->estimate = Estimate (IntersectionX (intersection), IntersectionY (intersection));
		intersection->closed = intersection->opened = false;
	}
	
	// Reset path state.
	path->open.first = path->open.last = path->closed.first = path->closed.last = 0;
	
	// Push the starting node(s) onto the open list.
	start->score = prynSquare (sx - IntersectionX (start));
	PushOpen (start);
	
	if (start->right && start->score)
	{
		start->right->score = prynSquare (sx - IntersectionX (start->right));
		PushOpen (start->right);
	}
	
	// Iteratively grow the best-scoring node until we exhaust the possibilities.
	while (path->open.first)
	{
		PrynEditorPathIntersection *lowest;
		size_t index = 0;
		
		// Find the node in the open list with the lowest score.
		lowest = path->open.first;
		prynDListEach (intersection, path->open)
			if (Score (intersection) < Score (lowest))
				lowest = intersection;
		
		// Reached the end; success.
		if (lowest == end)
		{
			for (intersection = lowest; intersection; intersection = intersection->last)
				index ++;
			prynAllocateCleared (state, sizeof (*route->steps) * index, (void **) &route->steps);
			if (!route->steps) return PrynResultOutOfMemory;
			route->count = index;
			index --;
			
			for (intersection = lowest; intersection; intersection = intersection->last)
			{
				PrynEditorPathStep *step = route->steps + index --;
				step->vertical = (intersection->last && (intersection->last == intersection->up || intersection->last == intersection->down));
				step->intersection = intersection;
				
				if (intersection->last)
				{
					if (intersection->last == intersection->right || intersection->last == intersection->left)
					{
						if (intersection->right) intersection->right->left = 0;
						if (intersection->left) intersection->left->right = 0;
						intersection->left = intersection->right = 0;
					}
					else if (intersection->last == intersection->up || intersection->last == intersection->down)
					{
						if (intersection->up) intersection->up->down = 0;
						if (intersection->down) intersection->down->up = 0;
						intersection->up = intersection->down = 0;
					}
				}
			}
			
			return PrynResultSuccess;
		}
		
		// Put the lowest-scorer in the closed list.
		prynDListRemove (path->open, lowest);
		PushClosed (lowest);
		
		// Iterate over the neighbours.
		for (index = 0; index < 4; index ++)
		{
			PrynEditorPathIntersection *neighbour;
			bool horizontal = false, better = false;
			size_t score;
			
			switch (index)
			{
				case 0: neighbour = lowest->left; horizontal = true; break;
				case 1: neighbour = lowest->right; horizontal = true; break;
				case 2: neighbour = lowest->up; break;
				case 3: neighbour = lowest->down; break;
			}
			
			if (!neighbour || neighbour->closed) continue;
			
			if (horizontal)
				score = lowest->score + prynSquare (IntersectionX (lowest) - IntersectionX (neighbour));
			else
				score = lowest->score + prynSquare (IntersectionY (lowest) - IntersectionY (neighbour));
			
			// Bump the score if we're taking a corner, thereby preferring fewer corners in the path.
			if (lowest->last)
			{
				if ((lowest->last == lowest->left || lowest->last == lowest->right) && (neighbour == lowest->up || neighbour == lowest->down))
					score *= 2;
				else if ((lowest->last == lowest->up || lowest->last == lowest->down) && (neighbour == lowest->left || neighbour == lowest->right))
					score *= 2;
			}
			
			// Move the neighbour onto the open list, or overwrite it if our score is better.
			if (!neighbour->opened)
			{
				PushOpen (neighbour);
				better = true;
			}
			else better = score < neighbour->score;
			
			if (better)
			{
				neighbour->last = lowest;
				neighbour->score = score;
			}
		}
	}
	
	// We ran out of elements; fail.
	return PrynResultDone;
}

PrynEditorExport (void, prynEditorPathDestroyRoute, (PrynState *state, PrynEditorPathRoute *route))
{
	if (!state || !route) return;
	prynFree (state, route->steps);
	prynClearMemory (route, sizeof (*route));
}

static PrynColor ChannelColor (int channel)
{
	switch (channel)
	{
		case 0: return prynColorRGB (255, 255, 255);
		case 1: return prynColorRGB (255, 0, 0);
		case 2: return prynColorRGB (0, 255, 0);
		case 3: return prynColorRGB (0, 0, 255);
		case 4: return prynColorRGB (255, 255, 0);
		case 5: return prynColorRGB (255, 0, 255);
		case 6: return prynColorRGB (0, 255, 255);
	}
}

PrynEditorExport (PrynResult, prynEditorPathPaintLattice, (PrynEditorPath *path, PrynGuiPainter *painter))
{
	PrynEditorPathIntersection *intersection;

	if (!path || !painter) return PrynResultNullArgument;
	
	prynGuiPainterSetPenSolid (painter, 1, prynColorWhite);
	prynGuiPainterSetBrushColor (painter, prynColorBlack);
		
	// Draw lines extending from each intersection to its neighbours.
	prynForEach (intersection, path->intersections, path->intersectionCount)
	{
		int x = IntersectionX (intersection), y = IntersectionY (intersection);
		
		if (intersection->down)
		{
			prynGuiPainterSetPenSolid (painter, 1, prynColorWhite);
			prynGuiPainterLine (painter, 2, x, y, x, IntersectionY (intersection->down));
		}
		
		if (intersection->right)
		{
			prynGuiPainterSetPenSolid (painter, 1, prynColorWhite);
			prynGuiPainterLine (painter, 2, x, y, IntersectionX (intersection->right), y);
		}
		/*if (intersection->up)
			prynGuiPainterLine (painter, 2, x, y, x, IntersectionY (intersection->up), y);
		if (intersection->left)
			prynGuiPainterLine (painter, 2, x, y, IntersectionX (intersection->left), y);*/
	}
	
	// Draw circles at the centre of each intersection.
	prynGuiPainterSetPenSolid (painter, 1, prynColorWhite);
	prynForEach (intersection, path->intersections, path->intersectionCount)
	{
		int x = path->verticals [intersection->vertical].offset, y = path->horizontals [intersection->horizontal].offset;
		prynGuiPainterCircle (painter, x, y, 3);
	}

	return PrynResultSuccess;
}

PrynEditorExport (PrynResult, prynEditorPathPaintRoute, (PrynEditorPathRoute *route, PrynGuiPainter *painter))
{
	PrynState *state = 0;
	PrynEditorPath *path;
	int sx, sy, ex, ey;

	if (!route || !painter) return PrynResultNullArgument;
	
	path = route->path;
	sx = route->sx, sy = route->sy, ex = route->ex, ey = route->ey;
	
	if (!route->count)
		prynGuiPainterLine (painter, 2, sx, sy, ex, ey);
	else
	{
		int *points;
		size_t index = 0, e, i, j;

		prynGuiPainterState (painter, &state);
		prynAllocate (state, sizeof (int) * (route->count + 5) * 2, (void **) &points);
		if (!points) return PrynResultOutOfMemory;
		
		#define p(X, Y) (points [index ++] = X, points [index ++] = Y)
		#define ix(INDEX) prynEditorPathIntersectionX (path, route->steps [INDEX].intersection)
		#define iy(INDEX) prynEditorPathIntersectionY (path, route->steps [INDEX].intersection)
		
		e = route->count - 1;
		
		p (sx, sy);
		p (sx, iy (0));
		
		for (i = 0, j = 1; i <= e; i ++, j ++)
		{
			PrynEditorPathStep *step = route->steps + i;
			PrynEditorPathIntersection *intersection = step->intersection;
			int x = ix (i), y = iy (i);
			
			p (x, y);
		}
		
		p (ex, iy (e));
		p (ex, ey);
		
		prynGuiPainterLineData (painter, index / 2, points);
		
		#undef iy
		#undef ix
		
		prynFree (state, points);
	}
	
	return PrynResultSuccess;
}

PrynEditorExport (PrynResult, prynEditorPathRouteDirty, (PrynEditorPathRoute *route, PrynGuiControl *control))
{
	PrynEditorPath *path;
	PrynEditorPathStep *step, *end;
	PrynRectangle area;

	if (!route || !control) return PrynResultNullArgument;
	path = route->path;
	if (!path) return PrynResultDone;
	
	#define dirty(LEFT, TOP, RIGHT, BOTTOM) \
		(prynRectangleContaining (&area, 2, LEFT, TOP, (RIGHT) - 1, (BOTTOM) - 1), \
		prynRectangleExpand (&area, &area, 1, 1), \
		prynGuiControlDirtyRectangle (control, &area))

	#define dirtyStep(STEPA, STEPB) dirty (IntersectionX ((STEPA)->intersection), IntersectionY ((STEPA)->intersection), IntersectionX ((STEPB)->intersection), IntersectionY ((STEPB)->intersection))
	
	dirty (route->sx, route->sy, IntersectionX (route->steps [0].intersection), IntersectionY (route->steps [0].intersection));
	
	for (step = route->steps, end = step + route->count - 1; step < end; step ++)
		dirtyStep (step, step + 1);
	dirty (route->ex, route->ey, IntersectionX (route->steps [route->count - 1].intersection), IntersectionY (route->steps [route->count - 1].intersection));
	
	#undef dirty
	#undef dirtyStep
	return PrynResultSuccess;
}
